/*
 * Copyright 2004-2008 H2 Group. Multiple-Licensed under the H2 License, Version 1.0, and under the Eclipse Public License, Version 1.0 (http://h2database.com/html/license.html). Initial Developer: H2 Group
 */
package jacky.song.android.util.lang;

/**
 * This utility class contains functions related to byte arrays.
 */
public class ByteUtils {
  
  private static final char[] HEX = "0123456789abcdef".toCharArray();
  
  private ByteUtils() {
  // utility class
  }
  
  public static byte[] toBigEndianBytes(long x) {
    byte[] arr = new byte[8];
    for (int i = 0; i < arr.length; i++) {
      arr[i] = (byte) (x >>> (64 - (i + 1) * 8));
    }
    return arr;
  }

  public static byte[] toBigEndianBytes(int x) {
    return new byte[] { (byte) (x >>> 24), (byte) (x >>> 16), (byte) (x >>> 8), (byte) x };
  }
  
  public static byte[] toBigEndianBytes(short x) {
    return new byte[] { (byte) (x >>> 8), (byte) x };
  }

  public static String readIntToHexString(byte[] buff, int pos) {
    return Integer.toHexString(readInt(buff, pos));
    //    StringBuilder sb = new StringBuilder();
    //    String hex = Integer.toHexString(readInt(buff, pos));
    //    for (int i = 0; i < hex.length(); i += 2)
    //      sb.insert(0, hex.substring(i, i + 2));
    //    return sb.toString();
  }

  public static int readInt(byte[] buff, int pos) {
    return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) + (buff[pos] & 0xff);
  }
  
  /**
   * Read a long value from the byte array at the given position. The most significant byte is read first.
   * 
   * @param buff
   *          the byte array
   * @param pos
   *          the position
   * @return the value
   */
  public static long readLong(byte[] buff, int pos) {
    return ((long) (readInt(buff, pos)) << 32) + (readInt(buff, pos + 4) & 0xffffffffL);
  }
  
  /**
   * Calculate the index of the first occurrence of the pattern in the byte array, starting with the given index. This methods returns -1 if the pattern has not been found, and the start position if the pattern is empty.
   * 
   * @param bytes
   *          the byte array
   * @param pattern
   *          the pattern
   * @param start
   *          the start index from where to search
   * @return the index
   */
  public static int indexOf(byte[] bytes, byte[] pattern, int start) {
    if (pattern.length == 0) { return start; }
    if (start > bytes.length) { return -1; }
    int last = bytes.length - pattern.length + 1;
    next: for (; start < last; start++) {
      for (int i = 0; i < pattern.length; i++) {
        if (bytes[start + i] != pattern[i]) {
          continue next;
        }
      }
      return start;
    }
    return -1;
  }
  
  /**
   * Convert a hex encoded string to a byte array.
   * 
   * @param s
   *          the hex encoded string
   * @return the byte array
   */
  public static byte[] convertStringToBytes(String s) {
    int len = s.length();
    if (len % 2 != 0) throw new RuntimeException("String must has even length");
    len /= 2;
    byte[] buff = new byte[len];
    for (int i = 0; i < len; i++) {
      buff[i] = (byte) ((getHexDigit(s, i + i) << 4) | getHexDigit(s, i + i + 1));
    }
    return buff;
  }
  
  private static int getHexDigit(String s, int i) {
    char c = s.charAt(i);
    if (c >= '0' && c <= '9') {
      return c - '0';
    }
    else if (c >= 'a' && c <= 'f') {
      return c - 'a' + 0xa;
    }
    else if (c >= 'A' && c <= 'F') {
      return c - 'A' + 0xa;
    }
    else {
      throw new RuntimeException("Can't get hex digit from " + s + " at " + i);
    }
  }
  
  /**
   * Calculate the hash code of the given byte array.
   * 
   * @param value
   *          the byte array
   * @return the hash code
   */
  public static int getByteArrayHash(byte[] value) {
    int len = value.length;
    int h = len;
    if (len < 50) {
      for (int i = 0; i < len; i++) {
        h = 31 * h + value[i];
      }
    }
    else {
      int step = len / 16;
      for (int i = 0; i < 4; i++) {
        h = 31 * h + value[i];
        h = 31 * h + value[--len];
      }
      for (int i = 4 + step; i < len; i += step) {
        h = 31 * h + value[i];
      }
    }
    return h;
  }
  
  /**
   * Convert a byte array to a hex encoded string.
   * 
   * @param value
   *          the byte array
   * @return the hex encoded string
   */
  public static String convertBytesToString(byte[] value) {
    return convertBytesToString(value, value.length);
  }
  
  /**
   * Convert a byte array to a hex encoded string.
   * 
   * @param value
   *          the byte array
   * @param len
   *          the number of bytes to encode
   * @return the hex encoded string
   */
  public static String convertBytesToString(byte[] value, int len) {
    char[] buff = new char[len + len];
    char[] hex = HEX;
    for (int i = 0; i < len; i++) {
      int c = value[i] & 0xff;
      buff[i + i] = hex[c >> 4];
      buff[i + i + 1] = hex[c & 0xf];
    }
    return new String(buff);
  }
  
  /**
   * Compare two byte arrays. This method will always loop over all bytes and doesn't use conditional operations in the loop to make sure an attacker can not use a timing attack when trying out passwords.
   * 
   * @param test
   *          the first array
   * @param good
   *          the second array
   * @return true if both byte arrays contain the same bytes
   */
  public static boolean compareSecure(byte[] test, byte[] good) {
    if ((test == null) || (good == null)) { return (test == null) && (good == null); }
    if (test.length != good.length) { return false; }
    if (test.length == 0) { return true; }
    // don't use conditional operations inside the loop
    int bits = 0;
    for (int i = 0; i < good.length; i++) {
      // this will never reset any bits
      bits |= test[i] ^ good[i];
    }
    return bits == 0;
  }
  
  /**
   * Set all elements of the array to zero.
   * 
   * @param buff
   *          the byte array
   */
  public static void clear(byte[] buff) {
    for (int i = 0; buff != null && i < buff.length; i++) {
      buff[i] = 0;
    }
  }
  
  /**
   * Compare the contents of two byte arrays. If the content or length of the first array is smaller than the second array, -1 is returned. If the content or length of the second array is smaller than the first array, 1 is returned. If the contents and lengths are the same, 0 is returned.
   * 
   * @param data1
   *          the first byte array (must not be null)
   * @param data2
   *          the second byte array (must not be null)
   * @return the result of the comparison (-1, 1 or 0)
   */
  public static int compareNotNull(byte[] data1, byte[] data2) {
    int len = Math.min(data1.length, data2.length);
    for (int i = 0; i < len; i++) {
      byte b = data1[i];
      byte b2 = data2[i];
      if (b != b2) { return b > b2 ? 1 : -1; }
    }
    int c = data1.length - data2.length;
    return c == 0 ? 0 : (c < 0 ? -1 : 1);
  }
  
  /**
   * Copy the contents of the source array to the target array. If the size if the target array is too small, a larger array is created.
   * 
   * @param source
   *          the source array
   * @param target
   *          the target array
   * @return the target array or a new one if the target array was too small
   */
  public static byte[] copy(byte[] source, byte[] target) {
    int len = source.length;
    if (len > target.length) {
      target = new byte[len];
    }
    System.arraycopy(source, 0, target, 0, len);
    return target;
  }
  
  /**
   * Create a new byte array and copy all the data. If the size of the byte array is zero, the same array is returned.
   * 
   * @param b
   *          the byte array (may not be null)
   * @return a new byte array
   */
  public static byte[] cloneByteArray(byte[] b) {
    if (b == null) { return null; }
    int len = b.length;
    if (len == 0) { return b; }
    byte[] copy = new byte[len];
    System.arraycopy(b, 0, copy, 0, len);
    return copy;
  }
  
  /**
   * Create an array of bytes with the given size. If this is not possible because not enough memory is available, an OutOfMemoryError with the requested size in the message is thrown.
   * 
   * @param len
   *          the number of bytes requested
   * @return the byte array
   * @throws OutOfMemoryError
   */
  public static byte[] newBytes(int len) {
    try {
      return new byte[len];
    }
    catch (OutOfMemoryError e) {
      Error e2 = new OutOfMemoryError("Requested memory: " + len);
      e2.initCause(e);
      throw e2;
    }
  }

  public static String toStringForm(byte[] arr) {
    StringBuilder sb = new StringBuilder();
    for (byte b : arr) {
      sb.append(((int) b) + "");
    }
    return sb.toString();
  }

}
